//***************************************************************************************
// MediaFrame is an Open Source streaming media platform in Java 
// which provides a fast, easy to implement and extremely small applet 
// that enables to view your audio/video content without having 
// to rely on external player applications or bulky plug-ins.
//
//--------------------------------------------------------------------------------------
//
// We changed a lot of code and added a lot of functionality.
// This includes, but not limited to, the following changes:
// 1. The project was renamed to MediaFrame;
// 2. The connection speed detection procedure was added;
// 3. The JavaScript API functions were added;
// 4. The pre and post image support functionality was added;
// 5. The ability to save movie into the local disk was added;
// 6. The inner buffer for a movie file was added;
// 7. The click-through functionality was added;    
// 8. The .zip files support was added;    
// 9. The realtime feedback agent functionality was added.    
// For the full list of the current functionality please visit the following web page:
// http://mediaframe.org/
//    
// 06 Jul 2002 - 19 Dec 2004 Konstantin Belous, Oleg Lebedev
//
//--------------------------------------------------------------------------------------
//
// This program is free software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the Free Software
// Foundation; either version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with this
// program; (See "LICENSE.GPL"). If not, write to the Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
//--------------------------------------------------------------------------------------
//
// If the program runs as Java applet it isn't "interactive" in the sense of the GNU
// General Public License. So paragraph 2c doesn't apply.
//
//**************************************************************************************
package mediaframe.mpeg1.audio.player;

import java.io.*;

/**
 * The <code>BigPipedInputStream</code> represents the Piped Input Stream.
 * It receives the input data from a <code>BigPipedOutputStream</code>, puts them into the circle data buffer
 * and returns when a client requests for the next portion of the data.<br/>
 * This class responds for convertion the audio samples into the uLaw representation for eight-bit eight khz ulaw decoder (browser).<br/>
 * This class implements volume and mute features (multiplies the digital sound data word by a volume value or returns 
 * uLaw zero if the mute equals true).  
 *
 * @author  Konstantin Belous
 * @version 1.0 26.07.2002
 *
 */
public class BigPipedInputStream extends InputStream {

	/** The pointer for read in the circle buffer. */	
	private int inp = 0;
	
	/** The pointer for write in the circle buffer. */	
	private int outp = 0;
	
	/** The value of the pointer for write in the circle buffer during last read from the stream. */	
	private int lastOutp = -1;

	/** The circle buffer of audio samples. */
	private short [] buffer;

	/** <tt>True</tt>, if the pipe has been already closed by <code>BigPipedOutputStream</code>. */
	boolean isClosedByWriter = false;

	/** <tt>True</tt>, if the pipe has been already closed by <code>BigPipedInputStream</code>. */
	private boolean isClosedByReader = false;

	/** The reference to the output stream in the pipe. */
	BigPipedOutputStream outputPipe = null;

	/** <tt>True</tt> if the audio player is playing the sound, <tt>false</tt> if the playback is paused. */
	public boolean playing = false;

	/** The volume of the audio signal. */
	public static int Volume = 256;
	
	/** The mute state of the audio player. */
	public static boolean mute = false;

	/** 
	 * <tt>True</tt>, if the decoding of the audio stream has been finished 
	 * and the circle buffer has been filled with the audio samples. 
	 */
	boolean bufferFilled = false;

	/** <tt>True</tt> for the first pause in the movie playback (normally if the player doesn't played any frames). */ 
	private boolean firstPause = true;

	/**
	 * Constructs a <code>BigPipedInputStream</code> class with the specified buffer size.
	 * @param nBufferSize the size of a circle buffer.
	 */
	public BigPipedInputStream(int nBufferSize) 
	{
		buffer = new short[nBufferSize];
	}

	/**
	 * Reads uLaw representation of the next sample of sound data from the circle buffer.
	 * Implements the volume feature (multiplies the sample value by a volume value) 
	 * or returns uLaw zero value if the mute equals true. 
	 * @return one byte from the circle buffer.
	 * @throws IOException raises if a error occurs.
	 */
	public int read () throws IOException {
		if(isClosedByWriter || isClosedByReader)
			throw new IOException("Pipe is closed!");

		notify_writer();
		
		if(outp >= buffer.length)
			outp = 0;

		if(inp == outp && bufferFilled)
			return ULAW_ZERO;

		while(inp == outp && ! isClosedByWriter && ! isClosedByReader) {
			wait_for_writer();
		}

		lastOutp = outp;
		
		int sample = (Volume * buffer[outp++]) >> 8;
	
    		int exponent;
 	   	int mantissa;
	
		if(! mute) {

		    sample = sample < -32767 ? -32767 : sample > 32767 ? 32767 : sample;

      		    if (sample >= 0) {
			if (sample > ULAW_CLIP)
	  			sample = ULAW_CLIP + ULAW_BIAS;
			else
	  			sample += ULAW_BIAS;
			exponent = ULAW_LUT[sample >> 7];
			mantissa = (sample >> (exponent + 3)) & 15;
			return ((exponent << 4 | mantissa) ^ 255);

      		    } else {

			if (sample < -ULAW_CLIP)
	  			sample = ULAW_CLIP + ULAW_BIAS;
			else
	  			sample = ULAW_BIAS - sample;
			exponent = ULAW_LUT[sample >> 7];
			mantissa = (sample >> (exponent + 3)) & 15;
			return ((exponent << 4 | mantissa) ^ 127);
      		    }
		}

		return ULAW_ZERO;

	}

//	private int loop = 0;

	/**
	 * Reads uLaw representations of <code>len</code> samples of sound data from the circle buffer 
	 * into the array <code>b</code> starting from the offset <code>off</code>.
	 * Implements the volume feature (multiplies the sample value by a volume value) 
	 * or returns uLaw zero value if the mute equals true.
	 * @param b the array into which the data is read.
	 * @param off the start offset in the array <code>b</code> into which the data is written.
	 * @param len the maximum amount of bytes to read.
	 * @return the total amount of bytes read into the array, or -1 if there is no more data because 
	 * 		   the end of the file has been reached. 
	 * @throws IOException raises if a error occurs.
	 */
	public int read(byte[] b, int off, int len) throws IOException {

//		System.out.println("Read " + len + " bytes from buffer! Inp = " + inp + " Outp = " + outp);

		try {
			while(!playing && !isClosedByReader && !isClosedByWriter && firstPause) {
		       	Thread.sleep(100);
			}
			firstPause = false;

			if(!playing && !isClosedByReader && !isClosedByWriter) {
//					System.out.println("stopped..." + (loop ++));
				for(int i = 0;i < len; i++) {
					b[i+off] = ULAW_ZERO;
				}
				return len;
			}

		} catch (InterruptedException IE) {
			throw new java.io.InterruptedIOException(IE.getMessage());
		}

		for(int i = 0;i < len; i++) {

			if(isClosedByWriter || isClosedByReader)
				return -1;
			b[i+off] = (byte) read();
		}
		return len;
	}
/*
	public int available() throws IOException {
		if(inp > outp)
			return inp - outp;

		if(inp == outp)
			return 0;

		return buffer.length + inp  - outp;
	}
*/

	/**
	 * Returns the amount of audio samples that could be placed into the circle buffer. 
	 */
	public int freeInBuffer() {
		if(inp >= outp)
			return buffer.length - inp  + outp - 1;

		return outp - inp - 1;
	}

	/**
	 * Connects this object with the specified <code>BigPipedOutputStream</code> object in the pipe.
	 * @param src the <code>BigPipedOutputStream</code> object.
	 */
	public void connect(BigPipedOutputStream src) {
		outputPipe = src;
		src.inputPipe = this;
	}

	/**
	 * Closes the <code>BigPipedInputStream</code>
	 */
	public void close() {
		isClosedByReader = true;
		notify_writer();
	}

	/**
	 * Places the next auduo sample into the circle buffer.
	 * @param b the next audio sample.
	 * @throws IOException raises if a error occurs.
	 */
	void receive(short b) throws IOException {
		if(isClosedByWriter || isClosedByReader)
			throw new IOException("Pipe is closed!");

		notify_reader();

		if(inp >= buffer.length)
			inp = 0;

		while(inp == lastOutp && ! isClosedByWriter && ! isClosedByReader) {
			wait_for_reader();
		}

		buffer[inp++] = b;
	}

	/** The amount of threads which waits the reader side. */
	private int waiting_reader = 0;
	/** The amount of threads which waits the writer side. */
	private int waiting_writer = 0;

	/**
	 * Notifies the writer side that the circle buffer is ready for receiving samples.
	 */
	public synchronized void notify_writer() {

		if (waiting_reader > 0)
			notifyAll();
	}

	/**
	 * Waits the reader until the circle buffer will be ready for receiving samples.
	 * @throws InterruptedIOException when the current process has been interrupted by the another process.
	 */
	public synchronized void wait_for_reader() throws InterruptedIOException {

		try {
			waiting_reader ++;
			wait();
			waiting_reader --;
		} catch (InterruptedException IE) {
			throw new InterruptedIOException(IE.toString());
		}
	}

	/**
	 * Notifies the reader side for about the next portion of data.
	 */
	public synchronized void notify_reader() {

		if (waiting_writer > 0)
			notifyAll();
	}

	/**
	 * Waits the writer for the next portion of data.
	 * @throws InterruptedIOException when the current process has been interrupted by the another process.
	 */
	public synchronized void wait_for_writer() throws InterruptedIOException {

		try {
			waiting_writer ++;
			wait();
			waiting_writer --;

		} catch (InterruptedException IE) {
			throw new InterruptedIOException(IE.toString());
		}
	}


  //
  // Tables and constants for mu-law conversion.
  //

  /**
   * uLaw representation of zero.
   * (Both 127 or 255 map to zero.)
   */
  public static final byte ULAW_ZERO = 127;
  /**
   * Constanta used in the linear -> uLaw computation.
   */
  public static final int ULAW_BIAS = 132;
  /**
   * Constanta used in the linear -> uLaw computation.
   */
  public static final int ULAW_CLIP = 32635;

  /**
   * Table of (roughly) logarithms base 2, used in the
   * linear -> uLaw computation.
   */
  public static final int[] ULAW_LUT = computeUlawLut();


  /**
   * Compute logarithm (roughly) table used in the linear -> uLaw mapping.
   * <p/>
   * We compute this table (rather than store it) mostly to make the
   * .class file a bit smaller, (and also to help avoid typos.)
   */
  private static int[] computeUlawLut() 
  {
    int[] table = new int[256];
    int val = 0;
    for (int i = 0; i < 256; i++) {
      if (i >> (val + 1) != 0)
		val++;
      table[i] = val;
    }
    return table;
  }

}

